package com.zhangmeng.tools.utils;

import com.zhangmeng.tools.dto.BucketFile;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import javafx.application.Platform;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tika.Tika;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class MinioUtils {

    public static void getInstance(String endpoint, String accessKey, String secretKey) {

        if (minioClient == null){
            minioClient(endpoint, accessKey, secretKey);
        }

    }
    public static Map<String,Object> objectMap = new HashMap<>();


    public static void reload(){
        if (minioClient != null){
            minioClient = null;
        }
        getDefault();
    }

    public static MinioClient getDefault() {
        if (minioClient == null) {
            Object endpoint = objectMap.get("endpoint");
            Object accessKey = objectMap.get("accessKey");
            Object secretKey = objectMap.get("secretKey");
            if (endpoint == null ){
                Platform.runLater(()->{
                    AlertUtils.alert_warning("配置文件：endpoint 为空");
                });
                return null;
            }
            if (accessKey == null ){
                Platform.runLater(()->{
                    AlertUtils.alert_warning("配置文件：accessKey 为空");
                });
                return null;
            }

            if (secretKey == null ){
                Platform.runLater(()->{
                    AlertUtils.alert_warning("配置文件：secretKey 为空");
                });
                return null;
            }
            minioClient = MinioClient.builder().endpoint(endpoint.toString()).credentials(accessKey.toString(), secretKey.toString()).build();
        }
        return minioClient;
    }

    public static String getFilePathByMd5(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }

    public static boolean checkChunk(String fileMd5, int chunkIndex,String bucket_name) {
        //得到分块文件目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunkIndex;

        //文件流
        InputStream fileInputStream = null;
        try {
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket_name)
                            .object(chunkFilePath)
                            .build());

            if (fileInputStream != null) {
                //分块已存在
                return  true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //分块未存在
        return false;
    }

    public static List<Item> getAllObjectsByPrefix(String bucketName,
                                                   String prefix,
                                                   boolean recursive) {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = getDefault().listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = null;
                try {
                    item = o.get();
                } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
                    e.printStackTrace();
                }
                list.add(item);
            }
        }
        return list;
    }

    public static List<BucketFile> getFileList(String bucketName) {

        List<Item> itemList = getAllObjectsByPrefix(bucketName, null, true);
        List<BucketFile> list = new ArrayList<>();
        if (itemList.size() > 0 ){
            list = itemList.stream().map(i -> {
                BucketFile bucketFile = new BucketFile();
                bucketFile.setBucketName(bucketName);
                bucketFile.setDir(i.isDir());
                bucketFile.setFileName(i.objectName());
                bucketFile.setSize(i.size());
                //获取url
                String presignedObjectUrl = getPresignedObjectUrl(bucketName, i.objectName());
                bucketFile.setUrl(presignedObjectUrl);
                return bucketFile;
            }).collect(Collectors.toList());
        }
        return list;
    }

    /**
     * 获得文件外链
     *
     * @param bucketName
     * @param objectName
     * @return url
     */
    public static String getPresignedObjectUrl(String bucketName, String objectName) {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .method(Method.GET).build();
        try {
            return getDefault().getPresignedObjectUrl(args);
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException | ServerException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static class FileChunkReader implements AutoCloseable {

        private final FileChannel channel;
        private final ByteBuffer buffer;
        private long chunkCount;
        private final int CHUNK_SIZE;
        private String name;
        private File file;
        private String extension;
        private String md5;

        public FileChunkReader(Path filePath, int chunkSize) throws IOException {
            this.channel = FileChannel.open(filePath, StandardOpenOption.READ);
            this.buffer = ByteBuffer.allocate(chunkSize);
            this.CHUNK_SIZE = chunkSize;
            long totalSize = channel.size(); // total size of the file in bytes
            this.chunkCount = (totalSize + CHUNK_SIZE - 1) / CHUNK_SIZE;
            this.file = filePath.toFile();
            this.name = this.file.getName();
            extension();//拓展名
            md5();
        }

        public void md5() throws IOException {
            this.md5 = DigestUtils.md5Hex(new FileInputStream(this.file));
        }

        public void extension() {
            this.extension = FilenameUtils.getExtension(file.getName());
        }

        public byte[] readNextChunk() throws IOException {
            buffer.clear();
            int bytesRead = channel.read(buffer);

            if (bytesRead == -1) {  // 文件结束
                return null;
            }

            buffer.flip();  // 准备从缓冲区读取数据

            byte[] chunk = new byte[bytesRead];
            buffer.get(chunk);
            return chunk;
        }

        public String getExtension() {
            return extension;
        }

        public void setExtension(String extension) {
            this.extension = extension;
        }

        public String getMd5() {
            return md5;
        }

        public void setMd5(String md5) {
            this.md5 = md5;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public FileChannel getChannel() {
            return channel;
        }

        public ByteBuffer getBuffer() {
            return buffer;
        }

        public long getChunkCount() {
            return chunkCount;
        }

        public void setChunkCount(long chunkCount) {
            this.chunkCount = chunkCount;
        }

        public int getCHUNK_SIZE() {
            return CHUNK_SIZE;
        }

        public File getFile() {
            return file;
        }

        public void setFile(File file) {
            this.file = file;
        }

        public void close() throws IOException {
            channel.close();
        }

    }

    private static MinioClient minioClient;

    public static MinioClient getMinioClient() {
        return minioClient;
    }

    private static void minioClient(String endpoint, String accessKey, String secretKey) {

        minioClient = MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }

    public static void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName, String contentType) {
        //转为流
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucket).object(objectName)
                    //-1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000，
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                    .contentType(contentType)
                    .build();

            minioClient.putObject(putObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //将文件上传到minIO，传入文件绝对路径
    public static void addMediaFilesToMinIO(String filePath, String bucket, String objectName) {
        try {
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectName)
                            .filename(filePath)
                            .build());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件到文件系统出错");
        }
    }

    public static File downloadFileFromMinIO(File file, String bucket, String objectName) {

        InputStream fileInputStream = null;
        OutputStream fileOutputStream = null;
        try {
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectName)
                            .build());
            try {
                fileOutputStream = new FileOutputStream(file);
                IOUtils.copy(fileInputStream, fileOutputStream);

            } catch (IOException e) {
                log.error("下载文件" + objectName + "出错");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件不存在" + objectName);
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return file;
    }

    //得到分块文件的目录
    public static String getChunkFileFolderPath(String fileMd5) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
    }

    public static final int chunkSize = 1024 * 1024 * 2;

    public static void test(String path){

        try (FileChunkReader reader = new FileChunkReader(Path.of(path), chunkSize);) { // 1MB chunk size
            byte[] chunk;
            //扩展名
            String extName = reader.getExtension();

            //合并为一个文件
            //创建临时文件作为合并文件
            File mergeFile = null;
            try {
                mergeFile = File.createTempFile(reader.getMd5(), "." + extName);
            } catch (IOException e) {
                e.printStackTrace();
                log.error("合并文件过程中创建临时文件出错");
            }

            byte[] b = new byte[1024 * 1024];
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
            while ((chunk = reader.readNextChunk()) != null) {
                // process the chunk
                String md5Hex = DigestUtils.md5Hex(chunk);
                System.out.println("--------------" + md5Hex);
                System.out.println("--------------" + reader.getChunkCount());

                //开始合并
                InputStream inputStream = new ByteArrayInputStream(chunk);
                int len = -1;
                while ((len = inputStream.read(b)) != -1) {
                    //向合并后的文件写
                    raf_write.write(b, 0, len);
                }
            }
            System.out.println(mergeFile);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 获得所有Bucket列表
     *
     * @return
     */
    public static List<Bucket> getAllBuckets() {
        try {
            return getDefault().listBuckets();
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
            e.printStackTrace();
            AlertUtils.alert_msg(objectMap.get("endpoint").toString() + "链接错误!");
        }
        return null;
    }

    public static void upload_file(File file,String bucketName){
        //文件名
        String fileName = file.getName();
        String newFileName = System.currentTimeMillis() + "." + StringUtils.substringAfterLast(fileName, ".");
        String contentType = getContentType(file);
        if (bucketName == null){
            AlertUtils.alert_warning("上传的bucket不能为空!");
            return;
        }
        uploadFile(bucketName, file, newFileName, contentType);
        AlertUtils.alert_msg("文件上传成功");
    }

    /**
     * 使用MultipartFile进行文件上传
     *
     * @param bucketName  存储桶
     * @param file        文件名
     * @param objectName  对象名
     * @param contentType 类型
     * @return
     */
    public static ObjectWriteResponse uploadFile(String bucketName, File file, String objectName, String contentType) {
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .contentType(contentType)
                            .stream(inputStream,inputStream.available(), -1)
                            .build());
        } catch (IOException | ServerException | InsufficientDataException | XmlParserException | InternalException | InvalidResponseException | InvalidKeyException | NoSuchAlgorithmException | ErrorResponseException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }


    public static String getContentType(File file){
        Tika tika = new Tika();
        try {
            return tika.detect(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 启动SpringBoot容器的时候初始化Bucket
     * 如果没有Bucket则创建
     *
     * @param bucketName
     */
    @SneakyThrows(Exception.class)
    public static void createBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            getDefault().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            success_msg(bucketName  + "创建成功！");
        }else {
            warning_msg(bucketName + "已存在");
        }
    }

    public static void warning_msg(String msg){
        Platform.runLater(()->{
            AlertUtils.alert_warning(msg );
        });
    }

    public static void success_msg(String msg){
        Platform.runLater(()->{
            AlertUtils.alert_msg(msg);
        });
    }

    /**
     * 判断Bucket是否存在，true：存在，false：不存在
     *
     * @param bucketName
     * @return
     */
    @SneakyThrows(Exception.class)
    public static boolean bucketExists(String bucketName) {
        return getDefault().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 根据bucketName删除Bucket，true：删除成功； false：删除失败，文件或已不存在
     *
     * @param bucketName
     * @throws Exception
     */
    @SneakyThrows(Exception.class)
    public static void removeBucket(String bucketName) {
        getDefault().removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 删除文件
     *
     * @param bucketName 存储桶
     * @param objectName 文件名称
     */
    @SneakyThrows(Exception.class)
    public static void removeFile(String bucketName, String objectName) {
        minioClient.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
    }

    /**
     * 批量删除文件
     *
     * @param bucketName 存储桶
     * @param keys       需要删除的文件列表
     * @return
     */
    public static void removeFiles(String bucketName, List<String> keys) {
        List<DeleteObject> objects = new LinkedList<>();
        keys.forEach(s -> {
            objects.add(new DeleteObject(s));
            try {
                removeFile(bucketName, s);
            } catch (Exception e) {
                log.error("[Minio工具类]>>>> 批量删除文件，异常：", e);
            }
        });
    }
}
